from system.action import Action
from system.object import Object
from system.eventServer import eventServer
from system.timeServer import timeServer
from system.fileServer import fileServer
from system import util

import pygame
import math

from OpenGL import GL
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

class PygameRenderer(Action):
    def run(self, args):
        self.gfxFps = args.get('gfxFps',60)
        self.inputFps = args.get('inputFps',100)
        self.res = args.get('res',(800,600))
        self.windowTitle = args.get('windowTitle','PASE')
        self.fullscreen = args.get('fullscreen',False)
        self.doLighting = args.get('doLighting',True)
        self.clearColor = args.get('clearColor',(0,0,1))
        self.lineWidth = args.get('lineWidth',2)
        self.ambientLight = args.get('ambientLight',(0.8,0.8,0.8))
        self.pickSize = args.get('pickSize',5)
        self.publishMouseMotion = args.get('publishMouseMotion',False)
        self.fov = args.get('fov',45)
        self.nearPlane = args.get('nearPlane',1)
        self.farPlane = args.get('farPlane',1000)
        
        self.initGfx()
        self.initInput()
        
        timeServer.subscribe(self.gfxFps,self.render)
        #timeServer.subscribe(self.inputFps,self.inputPump)
        eventServer.subscribe("update",self.inputPump)
        
    def kill(self):
        pygame.quit()
        
    def initGfx(self):
        pygame.init()
        glutInit(sys.argv)
        
        flags = pygame.OPENGL | pygame.DOUBLEBUF | pygame.HWSURFACE
        if self.fullscreen:
            flags |= pygame.FULLSCREEN
            
        self.scren = pygame.display.set_mode(self.res,flags)
        pygame.display.set_caption(self.windowTitle)
        
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_TEXTURE_2D)
        glShadeModel(GL_SMOOTH)
        if self.doLighting:
            glEnable(GL_LIGHTING)
            glLightModelfv(GL_LIGHT_MODEL_AMBIENT,self.ambientLight)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
        glLineWidth(self.lineWidth)
        glClearColor(self.clearColor[0],self.clearColor[1],self.clearColor[2],1)
        
        self.lists = {}
        self.textures = {}
        self.failedTextures = []
        self.maxLights = 8
        self.recordingName = self.recordingList = None
        
        self.render2dFunctions = util.getFunctions(self,"render2d_")
        self.render3dFunctions = util.getFunctions(self,"render3d_")
        self.unrender2dFunctions = util.getFunctions(self,"unrender2d_")
        self.unrender3dFunctions = util.getFunctions(self,"unrender3d_")
        
    def initInput(self):
        self.inputFunctions = util.getFunctions(self,'input_')
        self.mouseButtonNames = {1:'left',2:'middle',3:'right'}
        
        self.initKeys()
        self.initJoysticks()

    def initKeys(self):
        self.keys = {}
        for name in dir(pygame):
            if name.startswith('K_'):
                self.keys[getattr(pygame,name)] = name[2:].lower()
        
        self.keymods = {}
        for name in dir(pygame):
            if name.startswith("KMOD_"):
                self.keymods[name[5:].lower()] = getattr(pygame,name)
        
    def initJoysticks(self):
        self.joystickCount = pygame.joystick.get_count()
        if self.joystickCount:
            self.log("%d joysticks found" % self.joystickCount)
        
        self.joysticks = []
        for i in range(self.joystickCount):
            joystick = pygame.joystick.Joystick(i)
            joystick.init()
            self.joysticks.append(joystick)
            
    def inputPump(self, args):
        for event in pygame.event.get():
            eventName = pygame.event.event_name(event.type)
            eventName = eventName[0].lower() + eventName[1:]
            if eventName in self.inputFunctions:
                self.inputFunctions[eventName](event)
                
    def render(self, args):
        self.preRender()
        self.doRender(False)
        self.postRender()
        
    def doRender(self, picking):
        self.set3dProjection(picking)
        self.render3d(picking)
        self.set2dProjection(picking)
        self.render2d(picking)
        
    def preRender(self):
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT)
        self.rendered = set()
        
    def postRender(self):
        glFlush()
        pygame.display.flip()
        
    def set3dProjection(self, picking):
        if not picking:
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            gluPerspective(self.fov,float(self.res[0])/float(self.res[1]),self.nearPlane,self.farPlane)
            
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
        if self.doLighting:
            glEnable(GL_LIGHTING)
            
        glColor(1,1,1,1)
        self.unbindTexture()
        
    def set2dProjection(self, picking):
        if not picking:
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            gluOrtho2D(0,self.res[0],0,self.res[1])
            
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslate(0,self.res[1],0)
        glScalef(1,-1,1)
        
        if self.doLighting:
            glDisable(GL_LIGHTING)
            
        glColor(1,1,1,1)
        self.unbindTexture()
        
    def loadTexture(self, filename, transparent):
        path = fileServer.getFile(filename)
        if not path: return
        self.log("load texture", path)
        texture = pygame.image.load(path)
        w,h = texture.get_size()
        rgbTextureData = pygame.image.tostring(texture,"RGB",0)
        textureData = ""
        for i in range(0,len(textureData),3):
            r,g,b = [ord(j) for j in rbgTextureData[i:i+3]]
            if transparent and not r and not g and not b:
                a = 0
            else:
                a = 255
            textureData += chr(r) + chr(g) + chr(b) + chr(a)
        textureId = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D,textureId)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT)
        gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA,w,h,GL_RGBA,GL_UNSIGNED_BYTE,textureData)
        glBindTexture(GL_TEXTURE_2D,0)
        return textureId

    def bindTexture(self, filename, transparent = False):
        if filename in self.failedTextures:
            return False
        
        if not self.textures.has_key((filename,transparent)):
            texture = self.loadTexture(filename,transparent)
            if texture == None:
                self.failedTextures.append(filename)
                return False
            self.textures[(filename,transparent)] = texture
            
        id = self.textures[(filename,transparent)]
        glBindTexture(GL_TEXTURE_2D,id)
        return True

    def unbindTexture(self):
        glBindTexture(GL_TEXTURE_2D,0)
        
    def render3d(self, picking):
        pass
    
    def render2d(self, picking):
        for object in Object.searchAll(lambda obj: not obj.getVar("parent",None)):
            self.doRender2d(object,picking)
            
    def doRender2d(self, object, picking):
        if object not in self.rendered and object.getVar("render2d",None) and object.getVar("shown",True):
            self.rendered.add(object)
            
            if picking:
                self.pickMap[self.currentName] = object.name
                glPushName(self.currentName)
                self.currentName += 1
                
            doMatrix = object.hasVar("pos") or object.hasVar("scale") or object.hasVar("rot")
            if doMatrix:
                glPushMatrix()
                
                if object.hasVar("pos"):
                    x,y = object.getVar("pos",(0,0))
                    glTranslatef(x,y,0)
                        
                if object.hasVar("rot"):
                    glRotatef(math.degrees(object.getVar("rot",0)),0,0,1)
                    
                if object.hasVar("scale"):
                    x,y = object.getVar("scale",(1,1))
                    glScalef(x,y,1)
                    
            if object.name in self.lists:
                glCallList(self.lists[object.name])
            else:
                if False and object.getVar("static",True) and not self.recordingName and not self.recordingList:
                    self.recordingName = object.name
                    self.recordingList = glGenLists(1)
                    glNewList(self.recordingList,GL_COMPILE_AND_EXECUTE)
                
                doAttrib = object.hasVar("color") or object.hasVar("texture")
                if doAttrib:
                    glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT)
                    if object.hasVar("color"):
                        glColor(*object.getVar("color",(1,1,1)))
                    if object.hasVar("texture") and not picking:
                        self.bindTexture(object.getVar("texture",""),object.getVar("blendTexture",True))
                        
                render2d = object.getVar("render2d",None)
                if render2d in self.render2dFunctions:
                    self.render2dFunctions[render2d](object)
                
                for object in Object.searchAll(lambda obj: obj.getVar("parent",None) == object.name):
                    self.doRender2d(object,picking)
                    
                if render2d in self.unrender2dFunctions:
                    self.unrender2dFunctions[render2d](object)
                    
                if doAttrib:
                    glPopAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT)
                    
                if self.recordingName == object.name:
                    glEndList(self.recordingList)
                    self.lists[object.name] = self.recordingList
                    self.recordingList = self.recordingName = None
                
            if doMatrix:
                glPopMatrix()
                
            if picking:
                glPopName()
                
    def render2d_node(self):
        pass
    
    def render2d_circle(self, object):
        doCenterColor = object.hasVar("centerColor") and object.hasVar("color")
        
        if object.getVar("filled",True):
            if doCenterColor:
                glColor(*object.getVar("centerColor",(1,1,1,1)))
            
            glBegin(GL_TRIANGLE_FAN)
            glTexCoord2f(0.5,0.5)
            glVertex2f(0,0)
        else:
            glBegin(GL_LINE_LOOP)
            
        if doCenterColor:
            glColor(*object.getVar("color",(1,1,1,1)))
            
        slices = object.getVar("slices",10)
        for i in range(slices+1):
            th = float(i) / float(slices) * math.pi * 2
            x = math.cos(th)
            y = math.sin(th)
            glTexCoord2f((x/2.0) + 0.5, (y/2.0) + 0.5)
            glVertex2f(x,y)
        glEnd()
        
    def render2d_rect(self, object):
        if object.getVar("filled",True):
            glBegin(GL_QUADS)
        else:
            glBegin(GL_LINE_LOOP)
        glTexCoord2f(0,0)
        glVertex2f(0,0)
        glTexCoord2f(1,0)
        glVertex2f(1,0)
        glTexCoord2f(1,1)
        glVertex2f(1,1)
        glTexCoord2f(0,1)
        glVertex2f(0,1)
        glEnd()
        
    def render2d_tri(self, object):
        if object.getVar("filled",True):
            glBegin(GL_TRIANGLES)
        else:
            glBegin(GL_LINE_LOOP)
        glTexCoord2f(0,0)
        glVertex2f(0,-.5)
        glTexCoord2f(1,0.5)
        glVertex2f(1,0)
        glTexCoord2f(0,1)
        glVertex2f(0,.5)
        glEnd()
        
    def render2d_text(self, object):
        s = object.getVar("text",None)
        if s:
            if object.getVar("center",False):
                width = sum([glutBitmapWidth(GLUT_BITMAP_HELVETICA_18,ord(ch)) for ch in s])
                glRasterPos(-width/2.0,0,0)
            else:
                glRasterPos(0,0,0)
                
            for ch in s:
                glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,ord(ch))

    def render2d_menu(self, object):
        glPushMatrix()
        glScalef(self.res[0],self.res[1],1)
        
    def unrender2d_menu(self, object):
        glPopMatrix()
    
    def input_quit(self, evt):
        self.log("pygame quit")
        Object.shutdown = True
        
    def input_mouseButtonDown(self, event):
        buttonName, relPos = self.mouseInfo(event)
        self.publish("mouseButtonDown",button = event.button, pos = event.pos, relPos = relPos, buttonName = buttonName)
        
        self.doPicking(event.pos, event.button, "Down")
        
    def input_mouseButtonUp(self, event):
        buttonName, relPos = self.mouseInfo(event)
        self.publish("mouseButtonUp",button = event.button, pos = event.pos, relPos = relPos, buttonName = buttonName)
        
        self.doPicking(event.pos, event.button, "Up")
        
    def input_mouseMotion(self, event):
        if self.publishMouseMotion:
            self.publish("mouseMotion", pos = event.pos, relPos = self.relPos(event.pos), buttons = event.buttons)
     
    def input_keyDown(self, event):
        name, mods = self.keyInfo(event)
        self.publish("keyDown",name = name, mods = mods, key = event.key)
        
    def input_keyUp(self, event):
        name, mods = self.keyInfo(event)
        self.publish("keyUp",name = name, mods = mods, key = event.key)
        
    def input_joyButtonDown(self, event):
        self.publish("joyButtonDown",joy = event.joy, button = event.button)
        
    def input_joyButtonUp(self, event):
        self.publish("joyButtonup", joy = event.joy, button = event.button)
        
    def input_joyAxisMotion(self, event):
        self.publish("joyAxisMotion", joy = event.joy, value = event.value, axis = event.axis)
    
    def doPicking(self, pos, button, suffix):
        from OpenGL import GL
        if hasattr(GL,"glSelectWithCallback"):
            selectFunction = GL.glSelectWithCallback
        else:
            selectFunction = my_glSelectWithCallback
        
        self.pickMap = {}
        self.currentName = 1
        
        buf = list(selectFunction(pos[0],pos[1],lambda: self.doRender(True),self.pickSize,self.pickSize))
        
        hits = [i[2][0] for i in buf]
        if hits:
            for hit in hits:
                self.publish("objectClick" + suffix,button = button, pos = pos, \
                    relPos = self.relPos(pos), object = self.pickMap[hit])
                    
    def mouseInfo(self, event):
        buttonName = self.mouseButtonNames.get(event.button)
        relPos = (event.pos[0]/float(self.res[0]),event.pos[1]/float(self.res[1]))
        return buttonName, relPos

    def keyInfo(self, event):
        name = self.keys.get(event.key)
            
        mods = []
        for modName, mod in self.keymods.iteritems():
            if mod & event.mod:
                mods.append(modName)
                
        return name, mods
    
def my_glSelectWithCallback(x, y, callback, xsize = 5, ysize = 5, buffer_size = 512):
    viewport = glGetIntegerv(GL_VIEWPORT)
    glSelectBuffer(buffer_size)
    glRenderMode(GL_SELECT)
    glInitNames()
    glMatrixMode(GL_PROJECTION)
    previousviewmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
    glLoadIdentity()
    gluPickMatrix(x, viewport[3] - y, xsize, ysize, viewport)
    glMultMatrixd(previousviewmatrix)
    callback()
    glFlush()
    glMatrixMode(GL_PROJECTION)
    glLoadMatrixd(previousviewmatrix)
    return glRenderMode(GL_RENDER)
